New upstream version 2.6.0+ds
authorIOhannes m zmölnig (Debian/GNU) <umlaeute@debian.org>
Wed, 30 Apr 2025 07:39:57 +0000 (09:39 +0200)
committerIOhannes m zmölnig (Debian/GNU) <umlaeute@debian.org>
Wed, 30 Apr 2025 07:39:57 +0000 (09:39 +0200)
36 files changed:
CMakeLists.txt
docs/Build/Linux.md
docs/changelog.yml
externals/oscpp/include/oscpp/detail/endian.hpp
linux/Dockerfile.build
macos/meson_native_minversion_11.ini [deleted file]
macos/meson_native_minversion_12.ini [new file with mode: 0644]
macos/meson_native_universal_11.ini [deleted file]
macos/meson_native_universal_12.ini [new file with mode: 0644]
meson.build
src/AudioInterface.cpp
src/AudioInterface.h
src/AudioSocket.cpp
src/JackTrip.h
src/JackTripWorker.h
src/OscServer.cpp
src/OscServer.h
src/Regulator.cpp
src/RtAudioInterface.cpp
src/UdpHubListener.cpp
src/UdpHubListener.h
src/gui/qjacktrip.cpp
src/jacktrip_globals.h
src/vs/AudioSettings.qml
src/vs/ChangeDevices.qml
src/vs/Connected.qml
src/vs/CreateStudio.qml
src/vs/DeviceWarningModal.qml
src/vs/FeedbackSurvey.qml
src/vs/Footer.qml
src/vs/Setup.qml
src/vs/WebEngine.qml
src/vs/WebView.qml
src/vs/virtualstudio.cpp
src/vs/vsAudio.cpp
src/vs/vsDevice.cpp

index 442eaafbcd08d40740e38893d100a5621b458bd4..cb9eab7fcd4eb070807f51bbfafa1dfcd89b4568 100644 (file)
@@ -101,7 +101,7 @@ endif ()
 
 string(PREPEND QtVersion "Qt")
 
-if (${CMAKE_SYSTEM_NAME} MATCHES "Linux" OR ${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
+if (${CMAKE_SYSTEM_NAME} MATCHES "Linux" OR ${CMAKE_SYSTEM_NAME} MATCHES "Darwin" OR ${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD")
   find_package(PkgConfig REQUIRED)
   pkg_check_modules(JACK REQUIRED IMPORTED_TARGET jack)
   if (weakjack)
@@ -218,6 +218,7 @@ if (NOT nogui)
       src/vs/vsWebSocket.cpp
       src/vs/vsPermissions.cpp
       src/vs/vs.qrc
+      src/vs/WebSocketTransport.cpp
       src/images/images.qrc
       src/Analyzer.cpp
       src/Monitor.cpp
index 8086733c7ac42439a1b5f5f1481ff1084ca974a5..9f2621649a5416ee16d9a9ac7c78ced2a73516a5 100644 (file)
@@ -134,6 +134,7 @@ available:
 * MESON_ARGS - arguments to build using meson
 * QT_DOWNLOAD_URL - path to qt6 download (optional)
 * VST3SDK_DOWNLOAD_URL - path to the VST3 SDK (optional)
+* USE_SYSTEM_LIBSAMPLERATE - dynamically link with libsamplerate
 
 For example:
 
@@ -171,6 +172,7 @@ arm32 static
 ```
 docker buildx build --target=artifact -f linux/Dockerfile.build --output type=local,dest=./ \
   --platform linux/arm/v7 --build-arg BUILD_CONTAINER=debian:buster \
+  --build-arg USE_SYSTEM_LIBSAMPLERATE=1 \
   --build-arg MESON_ARGS="-Ddefault_library=static -Drtaudio=enabled -Drtaudio:jack=disabled -Drtaudio:default_library=static -Drtaudio:alsa=enabled -Drtaudio:pulse=disabled -Drtaudio:werror=false -Dnogui=true -Dcpp_link_args='-no-pie'" \
   --build-arg QT_DOWNLOAD_URL=https://files.jacktrip.org/contrib/qt/qt-5.15.13-static-linux-arm32.tar.gz .
 ```
index 6784c61ca5918455e4c8933d9d88f2437db63ada..82fd0a52ee50a6a2e33925ba3d0b7668695b799b 100644 (file)
@@ -1,3 +1,23 @@
+- Version: "2.6.0"
+  Date: 2025-04-22
+  Description:
+  - (added) OSC endpoint to get latencies for connected clients
+  - (updated) PLC auto headroom allows higher latency when necessary
+  - (updated) VS Mode allow any two consecutive channels for input
+  - (updated) VS Mode easier audio switching between stereo and mono
+  - (updated) VS Mode latency statistics now include jitter buffer
+  - (updated) VS Mode improvements to audio quality override settings
+  - (updated) VS Mode temporarily disabling feedback detection
+  - (fixed) VS Mode kicked out of sessions due to studio change
+  - (fixed) VS Mode recognizes changes to server host and port
+  - (fixed) VS Mode bugs with reconnecting due to audio changes
+  - (fixed) VS Mode strange error message during startup on Linux
+  - (fixed) VS Mode empty studio list when starting up
+  - (fixed) Ability to build VS Mode using CMake
+  - (fixed) Ability to build aarch64 or armv7 on Alpine Linux
+  - (fixed) Ability to build using Qt 6.9 release candidates
+  - (fixed) Ability to disable the use of libsamplerate
+  - (fixed) Ignore timestamps when generating jacktrip.1.gz
 - Version: "2.5.1"
   Date: 2025-01-30
   Description:
index f9a0c2e0439ba6bc0c0ae408ed6f7f29b04da413..bdb061c1bfdd036806b7f4bcb66d1b2391d62b4d 100644 (file)
@@ -71,7 +71,8 @@
     defined(__ia64__) || defined(_M_IX86) || defined(_M_IA64) ||     \
     defined(_M_ALPHA) || defined(__amd64) || defined(__amd64__) ||   \
     defined(_M_AMD64) || defined(__x86_64) || defined(__x86_64__) || \
-    defined(_M_X64) || defined(__bfin__)
+    defined(_M_X64) || defined(__bfin__) || defined(__aarch64__) || \
+    defined(__ARM_EABI__)
 
 #    define OSCPP_LITTLE_ENDIAN
 #    define OSCPP_BYTE_ORDER OSCPP_BYTE_ORDER_LITTLE_ENDIAN
index 4af7d420f0087930317ac989ef448ed395541e70..e0bdbae6bef3178d15338de3c59afeae2f647f6b 100644 (file)
@@ -3,9 +3,10 @@
 # this Dockerfile is used by GitHub CI to create linux builds
 # it requires these environment variables:
 #
-# BUILD_CONTAINER - Debian based container image to build with
-# MESON_ARGS      - arguments to build using meson
-# QT_DOWNLOAD_URL - path to qt download (optional)
+# BUILD_CONTAINER          - Debian based container image to build with
+# MESON_ARGS               - arguments to build using meson
+# QT_DOWNLOAD_URL          - path to qt download (optional)
+# USE_SYSTEM_LIBSAMPLERATE - dynamically link with libsamplerate
 
 # container image versions
 ARG BUILD_CONTAINER=ubuntu:20.04
@@ -25,6 +26,13 @@ RUN python3 -m pip install --upgrade pip \
 
 WORKDIR /opt/jacktrip
 
+# install libsamplerate
+ARG USE_SYSTEM_LIBSAMPLERATE=""
+ENV USE_SYSTEM_LIBSAMPLERATE=$USE_SYSTEM_LIBSAMPLERATE
+RUN if [ -n "$USE_SYSTEM_LIBSAMPLERATE" ]; then \
+  apt-get install -yq --no-install-recommends libsamplerate-dev; \
+  fi
+
 # install qt
 ARG QT_DOWNLOAD_URL=""
 ENV QT_DOWNLOAD_URL=$QT_DOWNLOAD_URL
diff --git a/macos/meson_native_minversion_11.ini b/macos/meson_native_minversion_11.ini
deleted file mode 100644 (file)
index 6d85821..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-[constants]
-minversion_args = ['-mmacosx-version-min=11']
-
-[built-in options]
-c_args = minversion_args
-cpp_args = minversion_args
-objcpp_args = minversion_args
-c_link_args = minversion_args
-cpp_link_args = minversion_args
-objcpp_link_args = minversion_args
diff --git a/macos/meson_native_minversion_12.ini b/macos/meson_native_minversion_12.ini
new file mode 100644 (file)
index 0000000..184e398
--- /dev/null
@@ -0,0 +1,10 @@
+[constants]
+minversion_args = ['-mmacosx-version-min=12']
+
+[built-in options]
+c_args = minversion_args
+cpp_args = minversion_args
+objcpp_args = minversion_args
+c_link_args = minversion_args
+cpp_link_args = minversion_args
+objcpp_link_args = minversion_args
diff --git a/macos/meson_native_universal_11.ini b/macos/meson_native_universal_11.ini
deleted file mode 100644 (file)
index c91f052..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-[constants]
-minversion_args = ['-mmacosx-version-min=11']
-universal_args = ['-arch', 'x86_64', '-arch', 'arm64']
-
-[built-in options]
-c_args = universal_args + minversion_args
-cpp_args = universal_args + minversion_args
-objcpp_args = universal_args + minversion_args
-c_link_args = universal_args + minversion_args
-cpp_link_args = universal_args + minversion_args
-objcpp_link_args = universal_args + minversion_args
diff --git a/macos/meson_native_universal_12.ini b/macos/meson_native_universal_12.ini
new file mode 100644 (file)
index 0000000..973f4b2
--- /dev/null
@@ -0,0 +1,11 @@
+[constants]
+minversion_args = ['-mmacosx-version-min=12']
+universal_args = ['-arch', 'x86_64', '-arch', 'arm64']
+
+[built-in options]
+c_args = universal_args + minversion_args
+cpp_args = universal_args + minversion_args
+objcpp_args = universal_args + minversion_args
+c_link_args = universal_args + minversion_args
+cpp_link_args = universal_args + minversion_args
+objcpp_link_args = universal_args + minversion_args
index 2bba8cbd520240fddf1d707a7d967f4815ee85e0..259fa8574051b317645d55a5279eae72d73fa31a 100644 (file)
@@ -281,7 +281,17 @@ if get_option('default_library') == 'static'
        qt_plugindir = run_command(qmake, '-query', 'QT_INSTALL_PLUGINS', check : true).stdout().strip()
        if qt_version == '6'
                # qt6 requires "Bundled*" modules for linking
-               static_deps += dependency('qt6', modules: ['DBus', 'BundledLibpng', 'BundledPcre2', 'BundledHarfbuzz', 'BundledZLIB'], include_type: 'system')
+               static_deps += compiler.find_library('Qt6BundledLibpng', required : true, dirs : [qt_libdir])
+               static_deps += compiler.find_library('Qt6BundledPcre2', required : true, dirs : [qt_libdir])
+               static_deps += compiler.find_library('Qt6BundledHarfbuzz', required : true, dirs : [qt_libdir])
+               zlib_dep = compiler.find_library('Qt6BundledZLIB', required : false, dirs : [qt_libdir])
+               if zlib_dep.found()
+                       static_deps += zlib_dep
+               endif
+               dbus_dep = compiler.find_library('Qt6DBus', required : false, dirs : [qt_libdir])
+               if dbus_dep.found()
+                       static_deps += dbus_dep
+               endif
        else
                static_deps += compiler.find_library('qtpcre2', required : true, dirs : [qt_libdir])
        endif
@@ -317,6 +327,8 @@ if get_option('default_library') == 'static'
                        static_link_args += ['-framework', 'Security']
                        static_link_args += ['-framework', 'GSS']
                        static_link_args += ['-framework', 'SystemConfiguration']
+                       static_link_args += ['-framework', 'UniformTypeIdentifiers']
+                       static_link_args += '-lresolv'
                        static_deps += dependency('zlib', required : true)
                endif
        endif
@@ -344,27 +356,31 @@ if rtaudio_dep.found() == false and jack_dep.found() == false
        configure.''')
 endif
 
-libsamplerate_dep = []
 found_libsamplerate = false
 if get_option('libsamplerate').allowed()
-       opt_var = cmake.subproject_options()
-       if get_option('buildtype') == 'release'
-               opt_var.add_cmake_defines({'CMAKE_BUILD_TYPE': 'Release'})
+       libsamplerate_dep = dependency('samplerate', required: false)
+       if libsamplerate_dep.found()
+               found_libsamplerate = true
        else
-               opt_var.add_cmake_defines({'CMAKE_BUILD_TYPE': 'Debug'})
-       endif
-       opt_var.add_cmake_defines({'CMAKE_POSITION_INDEPENDENT_CODE': 'ON'})
-       libsamplerate_subproject = cmake.subproject('libsamplerate', options: opt_var)
-       libsamplerate_dep = libsamplerate_subproject.dependency('samplerate')
-       found_libsamplerate = libsamplerate_dep.found()
-       if not found_libsamplerate and not get_option('libsamplerate').auto()
-               error('failed to configure libsamplerate')
-       endif
-       if found_libsamplerate
-               defines += '-DHAVE_LIBSAMPLERATE'
-               deps += libsamplerate_dep
+               opt_var = cmake.subproject_options()
+               if get_option('buildtype') == 'release'
+                       opt_var.add_cmake_defines({'CMAKE_BUILD_TYPE': 'Release'})
+               else
+                       opt_var.add_cmake_defines({'CMAKE_BUILD_TYPE': 'Debug'})
+               endif
+               opt_var.add_cmake_defines({'CMAKE_POSITION_INDEPENDENT_CODE': 'ON'})
+               libsamplerate_subproject = cmake.subproject('libsamplerate', options: opt_var)
+               libsamplerate_dep = libsamplerate_subproject.dependency('samplerate')
+               found_libsamplerate = libsamplerate_dep.found()
+               if not found_libsamplerate and not get_option('libsamplerate').auto()
+                       error('failed to configure libsamplerate')
+               endif
        endif
 endif
+if found_libsamplerate
+       defines += '-DHAVE_LIBSAMPLERATE'
+       deps += libsamplerate_dep
+endif
 
 if host_machine.system() == 'darwin'
        src += ['src/NoNap.mm']
@@ -530,7 +546,7 @@ if (host_machine.system() == 'linux')
                        custom_target('jacktrip.1.gz',
                                input: manfile,
                                output: 'jacktrip.1.gz',
-                               command: [gzip, '-k', '-f', '@INPUT@'],
+                               command: [gzip, '-k', '-f', '-n', '@INPUT@'],
                                install: true,
                                install_dir: get_option('mandir') / 'man1')
                endif
index 7450912403e2f5c08bac7c1643c6f956016a3219..044cdbb2a747c487ec5d8476f4afd6c0975a2993 100644 (file)
 #include "JackTrip.h"
 #include "ProcessPlugin.h"
 
+#if QT_VERSION < QT_VERSION_CHECK(6, 8, 0)
+#define STD_AS_CONST qAsConst
+#else
+#define STD_AS_CONST std::as_const
+#endif
+
 using std::cout;
 using std::endl;
 
@@ -76,6 +82,8 @@ AudioInterface::AudioInterface(QVarLengthArray<int> InputChans,
     , mMonitorStarted(false)
     , mJackTrip(jacktrip)
     , mInputMixMode(InputMixMode)
+    , mAudioInputLatency(0)
+    , mAudioOutputLatency(0)
     , mProcessingAudio(false)
 {
 }
@@ -189,7 +197,7 @@ void AudioInterface::audioInputCallback(QVarLengthArray<sample_t*>& in_buffer,
     }
 
 #ifndef WAIR
-    for (auto& s : qAsConst(mAudioSockets)) {
+    for (auto& s : STD_AS_CONST(mAudioSockets)) {
         s->getFromAudioSocketPlugin()->compute(n_frames, in_buffer.data(),
                                                in_buffer.data());
     }
@@ -204,7 +212,7 @@ void AudioInterface::audioInputCallback(QVarLengthArray<sample_t*>& in_buffer,
 #endif  // not WAIR
 
     // process incoming signal from audio interface using process plugins
-    for (auto& p : qAsConst(mProcessPluginsToNetwork)) {
+    for (auto& p : STD_AS_CONST(mProcessPluginsToNetwork)) {
         if (p->getInited()) {
             p->compute(n_frames, in_buffer.data(), in_buffer.data());
         }
@@ -266,7 +274,7 @@ void AudioInterface::audioOutputCallback(QVarLengthArray<sample_t*>& out_buffer,
     /// with one. do it chaining outputs to inputs in the buffers. May need a tempo buffer
 
 #ifndef WAIR  // NOT WAIR:
-    for (auto& p : qAsConst(mProcessPluginsFromNetwork)) {
+    for (auto& p : STD_AS_CONST(mProcessPluginsFromNetwork)) {
         if (p->getInited()) {
             p->compute(n_frames, out_buffer.data(), out_buffer.data());
         }
@@ -308,7 +316,7 @@ void AudioInterface::audioOutputCallback(QVarLengthArray<sample_t*>& out_buffer,
         }
     }
 
-    for (auto& s : qAsConst(mAudioSockets)) {
+    for (auto& s : STD_AS_CONST(mAudioSockets)) {
         s->getToAudioSocketPlugin()->compute(n_frames, out_buffer.data(),
                                              out_buffer.data());
     }
@@ -740,22 +748,22 @@ void AudioInterface::initPlugins(bool verbose)
                       << ") at sampling rate " << mSampleRate << "\n";
         }
 
-        for (auto& plugin : qAsConst(mProcessPluginsFromNetwork)) {
+        for (auto& plugin : STD_AS_CONST(mProcessPluginsFromNetwork)) {
             plugin->setOutgoingToNetwork(false);
             plugin->updateNumChannels(nChansIn, nChansOut);
             plugin->init(mSampleRate, mBufferSizeInSamples);
         }
-        for (auto& plugin : qAsConst(mProcessPluginsToNetwork)) {
+        for (auto& plugin : STD_AS_CONST(mProcessPluginsToNetwork)) {
             plugin->setOutgoingToNetwork(true);
             plugin->updateNumChannels(nChansIn, nChansOut);
             plugin->init(mSampleRate, mBufferSizeInSamples);
         }
-        for (auto& plugin : qAsConst(mProcessPluginsToMonitor)) {
+        for (auto& plugin : STD_AS_CONST(mProcessPluginsToMonitor)) {
             plugin->setOutgoingToNetwork(false);
             plugin->updateNumChannels(nChansMon, nChansMon);
             plugin->init(mSampleRate, mBufferSizeInSamples);
         }
-        for (auto& s : qAsConst(mAudioSockets)) {
+        for (auto& s : STD_AS_CONST(mAudioSockets)) {
             auto* plugin = s->getFromAudioSocketPlugin().get();
             plugin->setOutgoingToNetwork(true);
             plugin->updateNumChannels(nChansIn, nChansOut);
index 3de02af2af8859294fb5c84029628d60b2eeb8e2..886ad59ba80603ace09ed070c5697d70df4c0593 100644 (file)
@@ -315,6 +315,8 @@ class AudioInterface
     const std::string& getDevicesErrorMsg() const { return mErrorMsg; }
     const std::string& getDevicesWarningHelpUrl() const { return mWarningHelpUrl; }
     const std::string& getDevicesErrorHelpUrl() const { return mErrorHelpUrl; }
+    double getAudioInputLatency() const { return mAudioInputLatency; }
+    double getAudioOutputLatency() const { return mAudioOutputLatency; }
     bool highLatencyBufferSize() const { return getBufferSizeInSamples() > 256; }
     bool getHighLatencyFlag() const { return mHighLatencyFlag; }
     //------------------------------------------------------------------
@@ -370,6 +372,8 @@ class AudioInterface
    protected:
     JackTrip* mJackTrip;          ///< JackTrip Mediator Class pointer
     inputMixModeT mInputMixMode;  ///< Input mixing mode
+    double mAudioInputLatency;    ///< Latency of the audio input
+    double mAudioOutputLatency;   ///< Latency of the audio output
 
     void setDevicesWarningMsg(warningMessageT msg);
     void setDevicesErrorMsg(errorMessageT msg);
index 298f74da7d598286c50d36a8e06b5d18e37a324a..f1f6fb9f8fa25252bd85a21746fd8862fa8fc7ae 100644 (file)
@@ -585,7 +585,7 @@ void AudioSocketWorker::receiveAudio()
 void AudioSocketWorker::scheduleReconnect()
 {
     if (mRetryConnection) {
-        qDebug() << "Attempting to reconnect audio socket";
+        cout << "Attempting to reconnect audio socket" << endl;
         if (mTimerPtr.isNull()) {
             mTimerPtr.reset(new QTimer);
             QObject::connect(mTimerPtr.data(), &QTimer::timeout, this,
index 00b9cce1b640c9f9e5db4e4ff1b07cb6fed27428..5a1142f6aef1189d607c50ffe8ba1718e66b69ef 100644 (file)
@@ -551,6 +551,15 @@ class JackTrip : public QObject
         return (mAudioInterface == nullptr) ? false
                                             : mAudioInterface->getHighLatencyFlag();
     }
+    double getAudioInputLatency() const
+    {
+        return (mAudioInterface == nullptr) ? 0 : mAudioInterface->getAudioInputLatency();
+    }
+    double getAudioOutputLatency() const
+    {
+        return (mAudioInterface == nullptr) ? 0
+                                            : mAudioInterface->getAudioOutputLatency();
+    }
     double getLatency() const
     {
         return mReceiveRingBuffer == nullptr ? -1 : mReceiveRingBuffer->getLatency();
index 091f9836de53de1a359008d6ceba3bb20ef288ac..867586c2a84fce27b284f117167329b27ce4b1ee 100644 (file)
@@ -136,6 +136,12 @@ class JackTripWorker : public QObject
     uint16_t getClientPort() { return mClientPort; }
     QString getClientAddress() { return mClientAddress; }
 
+    double getLatency()
+    {
+        QMutexLocker lock(&mMutex);
+        return mJackTrip.isNull() ? -1 : mJackTrip->getLatency();
+    }
+
    private slots:
     void slotTest() { std::cout << "--- JackTripWorker TEST SLOT ---" << std::endl; }
     void receivedDataUDP();
index 5c3b61b7ba8ce1faa5e841f5dcd9a62bca5cbc26..30c94b6ba40ae5200e1010f1e2d383bd8de2a72a 100644 (file)
@@ -38,8 +38,7 @@
 
 #include <iostream>
 
-using std::cout;
-using std::endl;
+using namespace std;
 
 //*******************************************************************************
 OscServer::OscServer(quint16 port, QObject* parent) : QObject(parent), mPort(port) {}
@@ -91,10 +90,11 @@ void OscServer::readPendingDatagrams()
 
         mOscServerSocket->readDatagram(datagram.data(), datagram.size(), &sender,
                                        &senderPort);
-        qDebug() << "Received datagram from" << sender << ":" << senderPort;
-        qDebug() << "  - Data:" << datagram;
+        // qDebug() << "Received datagram from" << sender << ":" << senderPort;
+        // qDebug() << "  - Data:" << datagram;
 #ifndef NO_OSCPP
-        handlePacket(OSCPP::Server::Packet(datagram.data(), datagram.size()));
+        handlePacket(OSCPP::Server::Packet(datagram.data(), datagram.size()), sender,
+                     senderPort);
 #endif  // NO_OSCPP
         // Send a reply back to the client
         // QByteArray replyData("Reply from server");
@@ -104,7 +104,8 @@ void OscServer::readPendingDatagrams()
 
 //*******************************************************************************
 #ifndef NO_OSCPP
-void OscServer::handlePacket(const OSCPP::Server::Packet& packet)
+void OscServer::handlePacket(const OSCPP::Server::Packet& packet,
+                             const QHostAddress& sender, quint16 senderPort)
 {
     try {
         if (packet.isBundle()) {
@@ -115,7 +116,7 @@ void OscServer::handlePacket(const OSCPP::Server::Packet& packet)
 
             // Iterate over all the packets and call handlePacket recursively.
             while (!packets.atEnd()) {
-                handlePacket(packets.next());
+                handlePacket(packets.next(), sender, senderPort);
             }
         } else {
             // Convert to message
@@ -126,18 +127,47 @@ void OscServer::handlePacket(const OSCPP::Server::Packet& packet)
             if (msg == "/config") {
                 const char* key   = args.string();
                 const float value = args.float32();
-                cout << "Config received - key (" << key << ") value (" << value << ")"
-                     << endl;
+                cout << "OSC: Config received - key (" << key << ") value (" << value
+                     << ")" << endl;
                 if (strcmp("queueBuffer", key) == 0) {
                     emit signalQueueBufferChanged(static_cast<int>(value));
                 }
+            } else if (msg == "/get") {
+                const char* key = args.string();
+                cout << "OSC: Get request received - key (" << key << ")" << endl;
+                if (strcmp("latency", key) == 0) {
+                    emit signalLatencyRequested(sender, senderPort);
+                }
             } else {
                 // Simply print unknown messages
-                cout << "Unknown message:" << msg.address() << endl;
+                cerr << "OSC: Unknown message:" << msg.address() << endl;
             }
         }
-    } catch (std::exception& e) {
-        cout << "Exception:" << e.what() << endl;
+    } catch (exception& e) {
+        cerr << "OSC: Exception:" << e.what() << endl;
     }
 }
 #endif  // NO_OSCPP
+
+void OscServer::sendLatencyResponse(const QHostAddress& sender, quint16 senderPort,
+                                    QVector<QString>& clientNames,
+                                    QVector<double>& latencies)
+{
+#ifndef NO_OSCPP
+    QByteArray datagram;
+    datagram.resize(64 * 1024);
+
+    OSCPP::Client::Packet packet(datagram.data(), 64 * 1024);
+    packet.openBundle(QDateTime::currentSecsSinceEpoch());
+    packet.openMessage("/response/latency", clientNames.size() * 2);
+    for (int i = 0; i < clientNames.size(); i++) {
+        packet.string(clientNames[i].toStdString().c_str());
+        packet.float32(latencies[i]);
+    }
+    packet.closeMessage();
+    packet.closeBundle();
+
+    datagram.resize(packet.size());
+    mOscServerSocket->writeDatagram(datagram, sender, senderPort);
+#endif  // NO_OSCPP
+}
index 5b2b55cffe3f50b05370b745a02c8d285c25fcee..a860b383f5481f7eca7df5c3f92e9c3df20e0884 100644 (file)
 #ifndef __OSCSERVER_H__
 #define __OSCSERVER_H__
 
+#include <QHostAddress>
 #include <QObject>
+#include <QString>
 #include <QUdpSocket>
+#include <QVector>
 #include <QtCore>
 
 #ifndef NO_OSCPP
@@ -57,6 +60,8 @@ class OscServer : public QObject
     virtual ~OscServer();
     void start();
     void stop();
+    void sendLatencyResponse(const QHostAddress& sender, quint16 senderPort,
+                             QVector<QString>& clientNames, QVector<double>& latencies);
 
     static size_t makeConfigPacket(void* buffer, size_t size, const char* key,
                                    float value)
@@ -83,6 +88,7 @@ class OscServer : public QObject
     }
    signals:
     void signalQueueBufferChanged(int queueBufferSize);
+    void signalLatencyRequested(QHostAddress sender, quint16 senderPort);
 
    private slots:
     void readPendingDatagrams();
@@ -90,7 +96,8 @@ class OscServer : public QObject
    private:
     void closeSocket();
 #ifndef NO_OSCPP
-    void handlePacket(const OSCPP::Server::Packet& packet);
+    void handlePacket(const OSCPP::Server::Packet& packet, const QHostAddress& sender,
+                      quint16 senderPort);
 #endif  // NO_OSCPP
 
     QSharedPointer<QUdpSocket> mOscServerSocket;
index faffb2cf458b2b3a96cfa27e03d60d8192eadf52..39fe2f4dd3196e48cecef206c01f8140557c3339 100644 (file)
@@ -104,7 +104,7 @@ constexpr double AutoInitValFactor =
 // tweak
 constexpr int WindowDivisor = 8;  // for faster auto tracking
 constexpr double AutoHeadroomGlitchTolerance =
-    0.01;  // Acceptable rate of glitches before auto headroom is increased (1.0%)
+    0.006;  // Acceptable rate of glitches before auto headroom is increased (0.6%)
 constexpr double AutoHistoryWindow =
     60;  // rolling window of time (in seconds) over which auto tolerance roughly adjusts
 constexpr double AutoSmoothingFactor =
@@ -479,22 +479,23 @@ void Regulator::updateTolerance(int glitches, int skipped)
     // update headroom
     if (mAutoHeadroom < 0) {
         // variable headroom: automatically increase to minimize glitch counts
-        // only increase headroom if doing so would have reduced the number of
-        // glitches that occured over the past second by 1% or more.
-        // prevent headroom from growing beyond rolling average of max.
-        const int maxHeadroom = pushStat->longTermMax + 1;
         int glitchesAllowed;
         if (mMsecTolerance >= (mPeerFPPdurMsec * 2)) {
-            // calculate glitches allowed if tolerance if above or equal to duration of
-            // two packets
-            glitchesAllowed =
-                static_cast<int>(AutoHeadroomGlitchTolerance * mSampleRate / mPeerFPP);
+            // calculate glitches allowed if tolerance is above or equal to
+            // the duration of two packets
+            glitchesAllowed = std::ceil(
+                static_cast<float>(AutoHeadroomGlitchTolerance * mSampleRate) / mPeerFPP);
         } else {
             // zero glitches allowed if tolerance is below duration of two packets
             glitchesAllowed = 0;
             // also don't require two intervals in a row (override)
             mSkipAutoHeadroom = false;
         }
+        // sanity check: prevent headroom from growing beyond the greater of
+        // 3x rolling average of max, or 100ms
+        const int maxHeadroom = std::max<double>(pushStat->longTermMax * 3, 100.0);
+        // only increase headroom if glitch tolerance was exceeded and doing so
+        // would have reduced the number of glitches that occured over the past second.
         if (skipped > 0 && glitches > glitchesAllowed
             && mCurrentHeadroom + 1 <= maxHeadroom) {
             if (mSkipAutoHeadroom) {
index 8ff1599db70e5dced81de6be4b890bccaa1258dd..893c6b5650a9fbac446fceeb78cc1524acabaecc 100644 (file)
@@ -491,6 +491,15 @@ void RtAudioInterface::setup(bool verbose)
         setDevicesWarningMsg(AudioInterface::DEVICE_WARN_BUFFER_LATENCY);
     }
 
+    if (mDuplexMode) {
+        // duplex mode returns sum of input and output latencies
+        mAudioInputLatency  = static_cast<double>(mRtAudioInput->getStreamLatency()) / 2;
+        mAudioOutputLatency = mAudioInputLatency;
+    } else {
+        mAudioInputLatency  = mRtAudioInput->getStreamLatency();
+        mAudioOutputLatency = mRtAudioOutput->getStreamLatency();
+    }
+
     // Setup parent class
     // This MUST be after buffer size is finalized, so that plugins
     // are initialized with the correct settings
@@ -795,9 +804,6 @@ int RtAudioInterface::stopProcess()
         return (-1);
     }
 
-    AudioInterface::setDevicesWarningMsg(AudioInterface::DEVICE_WARN_NONE);
-    AudioInterface::setDevicesErrorMsg(AudioInterface::DEVICE_ERR_NONE);
-
     return 0;
 }
 
index 1d602dc9fda1f79bc2b1bca2a43214a72c723f68..e6fd7334b45a415a152007ebb3df46926eb03210 100644 (file)
@@ -379,6 +379,28 @@ void UdpHubListener::queueBufferChanged(int queueBufferSize)
     }
 }
 
+void UdpHubListener::handleLatencyRequest(const QHostAddress& sender, quint16 senderPort)
+{
+    QVector<QString> clientNames;
+    QVector<double> latencies;
+    getClientLatencies(clientNames, latencies);
+    if (mOscServer != nullptr) {
+        mOscServer->sendLatencyResponse(sender, senderPort, clientNames, latencies);
+    }
+}
+
+void UdpHubListener::getClientLatencies(QVector<QString>& clientNames,
+                                        QVector<double>& latencies)
+{
+    QMutexLocker lock(&mMutex);
+    for (int i = 0; i < gMaxThreads; i++) {
+        if (mJTWorkers->at(i) != nullptr) {
+            clientNames.append(mJTWorkers->at(i)->getAssignedClientName());
+            latencies.append(mJTWorkers->at(i)->getLatency());
+        }
+    }
+}
+
 //*******************************************************************************
 // Returns 0 on error
 int UdpHubListener::readClientUdpPort(QSslSocket* clientConnection, QString& clientName)
index 1570180f8007691c7026196e37d14789e1cba85f..84e53a71553332cbbc7ff92d765fc8a89bfc40fc 100644 (file)
 
 #include <QHostAddress>
 #include <QMutex>
+#include <QString>
 #include <QThread>
 #include <QThreadPool>
 #include <QUdpSocket>
+#include <QVector>
 #include <fstream>
 #include <iostream>
 #include <stdexcept>
@@ -86,6 +88,7 @@ class UdpHubListener : public QObject
 #endif
     int releaseThread(int id);
     void releaseDuplicateThreads(JackTripWorker* worker, uint16_t actual_peer_port);
+    void getClientLatencies(QVector<QString>& clientNames, QVector<double>& latencies);
 
     void setConnectDefaultAudioPorts(bool connectDefaultAudioPorts)
     {
@@ -111,6 +114,7 @@ class UdpHubListener : public QObject
     void receivedNewConnection();
     void stopCheck();
     void queueBufferChanged(int queueBufferSize);
+    void handleLatencyRequest(const QHostAddress& sender, quint16 senderPort);
 
    signals:
     void signalStarted();
@@ -139,6 +143,8 @@ class UdpHubListener : public QObject
 
         QObject::connect(mOscServer, &OscServer::signalQueueBufferChanged, this,
                          &UdpHubListener::queueBufferChanged, Qt::QueuedConnection);
+        QObject::connect(mOscServer, &OscServer::signalLatencyRequested, this,
+                         &UdpHubListener::handleLatencyRequest, Qt::QueuedConnection);
     };
 
     /**
index aec05463dca6efa81a082da2511c155c5f53ef99..78790fd33b3f119a3680e31c7142410b6a68e9ae 100644 (file)
 #include "RtAudio.h"
 #endif
 
+#if QT_VERSION < QT_VERSION_CHECK(6, 7, 0)
+#define QCHECKBOX_STATE_CHANGED QCheckBox::stateChanged
+#else
+#define QCHECKBOX_STATE_CHANGED QCheckBox::checkStateChanged
+#endif
+
 #include "../Compressor.h"
 #include "../CompressorPresets.h"
 #include "../Limiter.h"
@@ -119,14 +125,14 @@ QJackTrip::QJackTrip(UserInterface& interface, QWidget* parent)
                     m_ui->patchServerCheckBox->setEnabled(false);
                 }
             });
-    connect(m_ui->authCheckBox, &QCheckBox::stateChanged, this, [=]() {
+    connect(m_ui->authCheckBox, &QCHECKBOX_STATE_CHANGED, this, [=]() {
         m_ui->usernameLabel->setEnabled(m_ui->authCheckBox->isChecked());
         m_ui->usernameEdit->setEnabled(m_ui->authCheckBox->isChecked());
         m_ui->passwordLabel->setEnabled(m_ui->authCheckBox->isChecked());
         m_ui->passwordEdit->setEnabled(m_ui->authCheckBox->isChecked());
         credentialsChanged();
     });
-    connect(m_ui->requireAuthCheckBox, &QCheckBox::stateChanged, this, [=]() {
+    connect(m_ui->requireAuthCheckBox, &QCHECKBOX_STATE_CHANGED, this, [=]() {
         m_ui->certLabel->setEnabled(m_ui->requireAuthCheckBox->isChecked());
         m_ui->certEdit->setEnabled(m_ui->requireAuthCheckBox->isChecked());
         m_ui->certBrowse->setEnabled(m_ui->requireAuthCheckBox->isChecked());
@@ -138,21 +144,21 @@ QJackTrip::QJackTrip(UserInterface& interface, QWidget* parent)
         m_ui->credsBrowse->setEnabled(m_ui->requireAuthCheckBox->isChecked());
         authFilesChanged();
     });
-    connect(m_ui->ioStatsCheckBox, &QCheckBox::stateChanged, this, [=]() {
+    connect(m_ui->ioStatsCheckBox, &QCHECKBOX_STATE_CHANGED, this, [=]() {
         m_ui->ioStatsLabel->setEnabled(m_ui->ioStatsCheckBox->isChecked());
         m_ui->ioStatsSpinBox->setEnabled(m_ui->ioStatsCheckBox->isChecked());
         if (!m_ui->ioStatsCheckBox->isChecked()) {
             m_statsDialog->hide();
         }
     });
-    connect(m_ui->verboseCheckBox, &QCheckBox::stateChanged, this, [=]() {
+    connect(m_ui->verboseCheckBox, &QCHECKBOX_STATE_CHANGED, this, [=]() {
         gVerboseFlag = m_ui->verboseCheckBox->isChecked();
         if (!gVerboseFlag) {
             m_debugDialog->hide();
             m_debugDialog->clearOutput();
         }
     });
-    connect(m_ui->jitterCheckBox, &QCheckBox::stateChanged, this, [=]() {
+    connect(m_ui->jitterCheckBox, &QCHECKBOX_STATE_CHANGED, this, [=]() {
         m_ui->broadcastCheckBox->setEnabled(m_ui->jitterCheckBox->isChecked());
         m_ui->broadcastQueueLabel->setEnabled(m_ui->jitterCheckBox->isChecked()
                                               && m_ui->broadcastCheckBox->isChecked());
@@ -177,13 +183,13 @@ QJackTrip::QJackTrip(UserInterface& interface, QWidget* parent)
             m_autoQueueIndicator.setText(QStringLiteral("Auto queue: disabled"));
         }
     });
-    connect(m_ui->broadcastCheckBox, &QCheckBox::stateChanged, this, [=]() {
+    connect(m_ui->broadcastCheckBox, &QCHECKBOX_STATE_CHANGED, this, [=]() {
         m_ui->broadcastQueueLabel->setEnabled(m_ui->jitterCheckBox->isChecked()
                                               && m_ui->broadcastCheckBox->isChecked());
         m_ui->broadcastQueueSpinBox->setEnabled(m_ui->jitterCheckBox->isChecked()
                                                 && m_ui->broadcastCheckBox->isChecked());
     });
-    connect(m_ui->autoQueueCheckBox, &QCheckBox::stateChanged, this, [=]() {
+    connect(m_ui->autoQueueCheckBox, &QCHECKBOX_STATE_CHANGED, this, [=]() {
         m_ui->autoQueueLabel->setEnabled(m_ui->jitterCheckBox->isChecked()
                                          && m_ui->autoQueueCheckBox->isChecked());
         m_ui->autoQueueSpinBox->setEnabled(m_ui->jitterCheckBox->isChecked()
@@ -199,34 +205,34 @@ QJackTrip::QJackTrip(UserInterface& interface, QWidget* parent)
         }
     });
 
-    connect(m_ui->inFreeverbCheckBox, &QCheckBox::stateChanged, this, [=]() {
+    connect(m_ui->inFreeverbCheckBox, &QCHECKBOX_STATE_CHANGED, this, [=]() {
         m_ui->inFreeverbLabel->setEnabled(m_ui->inFreeverbCheckBox->isChecked());
         m_ui->inFreeverbWetnessSlider->setEnabled(m_ui->inFreeverbCheckBox->isChecked());
     });
-    connect(m_ui->inZitarevCheckBox, &QCheckBox::stateChanged, this, [=]() {
+    connect(m_ui->inZitarevCheckBox, &QCHECKBOX_STATE_CHANGED, this, [=]() {
         m_ui->inZitarevLabel->setEnabled(m_ui->inZitarevCheckBox->isChecked());
         m_ui->inZitarevWetnessSlider->setEnabled(m_ui->inZitarevCheckBox->isChecked());
     });
 
-    connect(m_ui->outFreeverbCheckBox, &QCheckBox::stateChanged, this, [=]() {
+    connect(m_ui->outFreeverbCheckBox, &QCHECKBOX_STATE_CHANGED, this, [=]() {
         m_ui->outFreeverbLabel->setEnabled(m_ui->outFreeverbCheckBox->isChecked());
         m_ui->outFreeverbWetnessSlider->setEnabled(
             m_ui->outFreeverbCheckBox->isChecked());
     });
-    connect(m_ui->outZitarevCheckBox, &QCheckBox::stateChanged, this, [=]() {
+    connect(m_ui->outZitarevCheckBox, &QCHECKBOX_STATE_CHANGED, this, [=]() {
         m_ui->outZitarevLabel->setEnabled(m_ui->outZitarevCheckBox->isChecked());
         m_ui->outZitarevWetnessSlider->setEnabled(m_ui->outZitarevCheckBox->isChecked());
     });
-    connect(m_ui->outLimiterCheckBox, &QCheckBox::stateChanged, this, [=]() {
+    connect(m_ui->outLimiterCheckBox, &QCHECKBOX_STATE_CHANGED, this, [=]() {
         m_ui->outLimiterLabel->setEnabled(m_ui->outLimiterCheckBox->isChecked());
         m_ui->outClientsSpinBox->setEnabled(m_ui->outLimiterCheckBox->isChecked());
     });
 
-    connect(m_ui->connectScriptCheckBox, &QCheckBox::stateChanged, this, [=]() {
+    connect(m_ui->connectScriptCheckBox, &QCHECKBOX_STATE_CHANGED, this, [=]() {
         m_ui->connectScriptEdit->setEnabled(m_ui->connectScriptCheckBox->isChecked());
         m_ui->connectScriptBrowse->setEnabled(m_ui->connectScriptCheckBox->isChecked());
     });
-    connect(m_ui->disconnectScriptCheckBox, &QCheckBox::stateChanged, this, [=]() {
+    connect(m_ui->disconnectScriptCheckBox, &QCHECKBOX_STATE_CHANGED, this, [=]() {
         m_ui->disconnectScriptEdit->setEnabled(
             m_ui->disconnectScriptCheckBox->isChecked());
         m_ui->disconnectScriptBrowse->setEnabled(
@@ -426,7 +432,7 @@ void QJackTrip::showEvent(QShowEvent* event)
                     "will automatically be re-enabled.)");
                 msgBox.setWindowTitle(QStringLiteral("JACK Not Available"));
                 msgBox.setCheckBox(dontBugMe);
-                QObject::connect(dontBugMe, &QCheckBox::stateChanged, this, [=]() {
+                QObject::connect(dontBugMe, &QCHECKBOX_STATE_CHANGED, this, [=]() {
                     m_hideWarning = dontBugMe->isChecked();
                 });
                 msgBox.exec();
index dbeef2a55071dadf62268ba9259a4e2de6b71709..aa9f9e911100703c0c904efdcb735ab879a91ec8 100644 (file)
@@ -40,7 +40,7 @@
 
 #include "jacktrip_types.h"
 
-constexpr const char* const gVersion = "2.5.1";  ///< JackTrip version
+constexpr const char* const gVersion = "2.6.0";  ///< JackTrip version
 
 //*******************************************************************************
 /// \name Default Values
index a02a8de3f393dce9e2e1885aab8f855069a2b0f0..447d97a41179b7ee371307217029300264dac513 100644 (file)
@@ -452,7 +452,6 @@ Rectangle {
                 delegate: ItemDelegate {
                     required property var modelData
                     required property int index
-                    width: parent.width
                     contentItem: Text {
                         text: modelData.label
                     }
index dcff43487be15f032013540b97d2f76f1b1f8f7f..349e1f8625b9daf69d4e94705f8d450922820bb6 100644 (file)
@@ -41,11 +41,17 @@ Rectangle {
 
     property string linkText: virtualstudio.darkMode ? "#8B8D8D" : "#272525"
 
+    property bool autoQueueBuffer: virtualstudio.queueBuffer == 0
+
     function getQueueBufferString () {
-        if (virtualstudio.queueBuffer == 0) {
+        let queueBuffer = virtualstudio.queueBuffer;
+        if (useStudioQueueBuffer.checkState == Qt.Checked) {
+            queueBuffer = virtualstudio.currentStudio.queueBuffer;
+        }
+        if (queueBuffer == 0) {
             return "auto";
         }
-        return virtualstudio.queueBuffer + " ms";
+        return queueBuffer + " ms";
     }
 
     MouseArea {
@@ -132,7 +138,7 @@ Rectangle {
         CheckBox {
             id: useStudioQueueBuffer
             checked: virtualstudio.useStudioQueueBuffer
-            text: qsTr("Use Studio settings")
+            text: qsTr("Use Studio settings (recommended)")
             anchors.top: latencyDivider.bottom
             anchors.topMargin: 16 * virtualstudio.uiScale
             x: 168 * virtualstudio.uiScale;
@@ -167,32 +173,73 @@ Rectangle {
         }
 
         Text {
-            id: currentLatency
+            id: queueBufferText
             anchors.top: latencyDivider.bottom
             anchors.topMargin: 16 * virtualstudio.uiScale
             anchors.right: parent.right
             anchors.rightMargin: 24 * virtualstudio.uiScale
-            text: "Buffer Latency: " + Math.round(virtualstudio.networkStats.recvLatency) + " ms"
+            text: "Audio Quality: " + getQueueBufferString()
+            font { family: "Poppins"; pixelSize: fontSmall * virtualstudio.fontScale * virtualstudio.uiScale }
+            color: textColour
+        }
+
+        Text {
+            id: currentLatency
+            anchors.top: queueBufferText.bottom
+            anchors.topMargin: 6 * virtualstudio.uiScale
+            anchors.right: parent.right
+            anchors.rightMargin: 24 * virtualstudio.uiScale
+            text: "Ingress Jitter Latency: " + Math.round(virtualstudio.networkStats.clientBufferLatency) + " ms"
             font { family: "Poppins"; pixelSize: fontSmall * virtualstudio.fontScale * virtualstudio.uiScale }
             color: textColour
         }
 
+        Button {
+            id: queueBufferAutoButton
+            width: 60 * virtualstudio.uiScale
+            height: 30 * virtualstudio.uiScale
+            anchors.top: useStudioQueueBuffer.bottom
+            anchors.topMargin: 16 * virtualstudio.uiScale
+            anchors.left: useStudioQueueBuffer.left
+            background: Rectangle {
+                radius: 6 * virtualstudio.uiScale
+                color: queueBufferAutoButton.down ? browserButtonPressedColour : (queueBufferAutoButton.hovered ? browserButtonHoverColour : (autoQueueBuffer ? "#FF0000" : browserButtonColour))
+            }
+            onClicked: {
+                if (autoQueueBuffer) {
+                    virtualstudio.queueBuffer = 5;
+                } else {
+                    virtualstudio.queueBuffer = 0;
+                }
+                autoQueueBuffer = !autoQueueBuffer;
+            }
+            Text {
+                text: "Auto"
+                font { family: "Poppins"; pixelSize: fontMedium * virtualstudio.fontScale * virtualstudio.uiScale}
+                anchors { horizontalCenter: parent.horizontalCenter; verticalCenter: parent.verticalCenter }
+                color: textColour
+            }
+
+            visible: useStudioQueueBuffer.checkState != Qt.Checked
+        }
+
         Slider {
             id: queueBufferSlider
             value: virtualstudio.queueBuffer
             onMoved: {
                 virtualstudio.queueBuffer = value;
             }
-            from: 0
+            from: 1
             to: 250
             stepSize: 1
             padding: 0
-            visible: useStudioQueueBuffer.checkState != Qt.Checked
+            visible: !autoQueueBuffer && useStudioQueueBuffer.checkState != Qt.Checked
 
             anchors.top: useStudioQueueBuffer.bottom
             anchors.topMargin: 16 * virtualstudio.uiScale
-            x: queueBufferText.x + queueBufferText.width
-            width: parent.width - x - (16 * virtualstudio.uiScale) - queueBufferText.width;
+            anchors.left: queueBufferAutoButton.right
+            anchors.leftMargin: 8 * virtualstudio.uiScale
+            width: parent.width - x - (16 * virtualstudio.uiScale);
 
             background: Rectangle {
                 x: queueBufferSlider.leftPadding
@@ -224,14 +271,27 @@ Rectangle {
         }
 
         Text {
-            id: queueBufferText
-            width: (64 * virtualstudio.uiScale)
-            anchors.left: useStudioQueueBuffer.left
-            anchors.verticalCenter: queueBufferSlider.verticalCenter
-            text: getQueueBufferString()
-            font { family: "Poppins"; pixelSize: fontMedium * virtualstudio.fontScale * virtualstudio.uiScale }
+            id: lowerLatencyText
+            anchors.top: queueBufferSlider.bottom
+            anchors.topMargin: 8 * virtualstudio.uiScale
+            anchors.left: queueBufferSlider.left
+            anchors.leftMargin: 8 * virtualstudio.uiScale
+            text: "Lower Latency"
+            font { family: "Poppins"; pixelSize: fontSmall * virtualstudio.fontScale * virtualstudio.uiScale }
             color: textColour
-            visible: useStudioQueueBuffer.checkState != Qt.Checked
+            visible: !autoQueueBuffer && useStudioQueueBuffer.checkState != Qt.Checked
+        }
+
+        Text {
+            id: higherQualityText
+            anchors.top: queueBufferSlider.bottom
+            anchors.topMargin: 8 * virtualstudio.uiScale
+            anchors.right: queueBufferSlider.right
+            anchors.rightMargin: 8 * virtualstudio.uiScale
+            text: "Higher Quality"
+            font { family: "Poppins"; pixelSize: fontSmall * virtualstudio.fontScale * virtualstudio.uiScale }
+            color: textColour
+            visible: !autoQueueBuffer && useStudioQueueBuffer.checkState != Qt.Checked
         }
     }
 
index b95af9f8af4fedc13f76af757fc9dd34140460fc..85e73a141b7398df4748a482197993fbf114db54 100644 (file)
@@ -60,6 +60,10 @@ Item {
 
     property bool isUsingRtAudio: audio.audioBackend == "RtAudio"
 
+    function hasStudioId () {
+        return typeof virtualstudio.currentStudio.id === 'string' && virtualstudio.currentStudio.id !== null && virtualstudio.currentStudio.id.length > 0
+    }
+
     Loader {
         id: studioWebLoader
         anchors.top: parent.top
@@ -67,10 +71,7 @@ Item {
         anchors.left: parent.left
         anchors.bottom: deviceControlsGroup.top
 
-        property string accessToken: auth.isAuthenticated && Boolean(auth.accessToken) ? auth.accessToken : ""
-        property string studioId: virtualstudio.currentStudio.id
-
-        source: accessToken && studioId ? "Web.qml" : "WebNull.qml"
+        source: auth.isAuthenticated && hasStudioId() ? "Web.qml" : "WebNull.qml"
     }
 
     DeviceControlsGroup {
index 7458dd683bd55a2a23d19c44cc8da22b65f2106f..c796dae00a147bba38e3535caa01077a053bc992 100644 (file)
@@ -20,8 +20,7 @@ Item {
         anchors.right: parent.right
         anchors.left: parent.left
         anchors.bottom: footer.top
-        property string accessToken: auth.isAuthenticated && Boolean(auth.accessToken) ? auth.accessToken : ""
-        sourceComponent: virtualstudio.windowState === "create_studio" && accessToken ? createStudioWeb : createStudioNull
+        sourceComponent: virtualstudio.windowState === "create_studio" && auth.isAuthenticated ? createStudioWeb : createStudioNull
     }
 
     Component {
@@ -41,7 +40,7 @@ Item {
             settings.javascriptCanPaste: true
             settings.screenCaptureEnabled: true
             profile.httpUserAgent: `JackTrip/${virtualstudio.versionString}`
-            url: `https://${virtualstudio.apiHost === "test.jacktrip.com" ? "next-test.jacktrip.com" : "www.jacktrip.com"}/app/studios/create?accessToken=${accessToken}&userId=${auth.userId}`
+            url: `https://${virtualstudio.apiHost === "test.jacktrip.com" ? "next-test.jacktrip.com" : "www.jacktrip.com"}/app/studios/create`
 
             onContextMenuRequested: function(request) {
                 // this disables the default context menu: https://doc.qt.io/qt-6.2/qml-qtwebengine-contextmenurequest.html#accepted-prop
index f56dfa7ac45bcc27d96cb877ed039ecb43e6dcd6..66fa6168d14042c77a721968619f0f60c4163935 100644 (file)
@@ -132,7 +132,6 @@ Item {
                     onClicked: () => {
                         deviceWarningPopup.close();
                         audio.stopAudio(true);
-                        virtualstudio.studioToJoin = virtualstudio.currentStudio.id;
                         virtualstudio.windowState = "connected";
                         virtualstudio.saveSettings();
                         virtualstudio.joinStudio();
index bab87a39a9ef4a5b0936c4a71bd597f92e7ac849..678b503d5fdf482acf76df7eca0eaada671312ae 100644 (file)
@@ -371,7 +371,7 @@ Item {
                     anchors.topMargin: 16 * virtualstudio.uiScale
                     anchors.horizontalCenter: parent.horizontalCenter
                     width: parent.width
-                    text: "Your feedback has been recorded."
+                    text: "Your feedback has been sent."
                     font {family: "Poppins"; pixelSize: fontSmall * virtualstudio.fontScale * virtualstudio.uiScale }
                     horizontalAlignment: Text.AlignHCenter
                     color: textColour
index 18f82923343332c491cc295afc62752c346ec137..5b85ead6e54d554a736ae9609f1df176463d25f5 100644 (file)
@@ -31,6 +31,7 @@ Rectangle {
         let minRtt = virtualstudio.networkStats.minRtt;
         let maxRtt = virtualstudio.networkStats.maxRtt;
         let avgRtt = virtualstudio.networkStats.avgRtt;
+        let clientBufferLatency = virtualstudio.networkStats.clientBufferLatency;
 
         let texts = ["Unstable", "Please plug into Ethernet & turn off WIFI.", meterRed];
         if (virtualstudio.networkOutage) {
@@ -42,16 +43,16 @@ Rectangle {
             return texts;
         }
 
-        texts[1] = "<b>" + minRtt + " ms - " + maxRtt + " ms</b>, avg " + avgRtt + " ms";
+        texts[1] = "<b>" + minRtt + " - " + maxRtt + " ms ping</b>, " + clientBufferLatency + " ms jitter";
         let quality = "Poor";
         let color = meterRed;
-        if (avgRtt < 10 && maxRtt < 15) {
+        if (avgRtt < 10 && maxRtt < 15 && clientBufferLatency < 6) {
             quality = "Excellent";
             color = meterGreen;
-        } else if (avgRtt < 20 && maxRtt < 30) {
+        } else if (avgRtt < 20 && maxRtt < 30 && clientBufferLatency < 9) {
             quality = "Good";
             color = meterYellow;
-        } else if (avgRtt < 30 && maxRtt < 40) {
+        } else if (avgRtt < 30 && maxRtt < 40 && clientBufferLatency < 12) {
             quality = "Fair";
             color = statsOrange;
         }
index a28a8ddf54946099343b737b348381f74f7470a4..e2fe2cfaa5a449a5dd049e649788b8a7164fc3b2 100644 (file)
@@ -140,7 +140,6 @@ Item {
                         deviceWarningModal.open();
                     } else {
                         audio.stopAudio(true);
-                        virtualstudio.studioToJoin = virtualstudio.currentStudio.id;
                         virtualstudio.windowState = "connected";
                         virtualstudio.saveSettings();
                         virtualstudio.joinStudio();
index 2f157641ccd8a1e4c8252142f45af7b8980ba942..192d184643426a6ded94a386ce449f4c83fbef3b 100644 (file)
@@ -52,7 +52,6 @@ Item {
         anchors.fill: parent
         color: backgroundColour
 
-        property string accessToken: auth.isAuthenticated && Boolean(auth.accessToken) ? auth.accessToken : ""
         property string studioId: virtualstudio.currentStudio.id
 
         WebEngineView {
@@ -62,7 +61,7 @@ Item {
             settings.javascriptCanPaste: true
             settings.screenCaptureEnabled: true
             profile.httpUserAgent: `JackTrip/${virtualstudio.versionString}`
-            url: `https://${virtualstudio.apiHost}/studios/${studioId}/live`
+            url: `https://${virtualstudio.apiHost}/studios/${web.studioId}/live`
 
             // useful for debugging
             // onJavaScriptConsoleMessage: function(level, message, lineNumber, sourceID) {
index 871a1d9eccba3720e85291b3525a52413f31e611..8590542caceeed7bdea1eca626ed3d549d11e9b6 100644 (file)
@@ -10,14 +10,13 @@ Item {
         id: web
         anchors.fill: parent
 
-        property string accessToken: auth.isAuthenticated && Boolean(auth.accessToken) ? auth.accessToken : ""
         property string studioId: virtualstudio.currentStudio.id
 
         WebView {
             id: webEngineView
             anchors.fill: parent
             httpUserAgent: `JackTrip/${virtualstudio.versionString}`
-            url: `https://${virtualstudio.apiHost}/studios/${studioId}/live?accessToken=${accessToken}`
+            url: `https://${virtualstudio.apiHost}/studios/${web.studioId}/live`
         }
     }
 }
index bef8af4b85d5ba5aeb945c846a5e98c171ec78fe..7ba6368c6056beef9534533ea92125ff46c62291 100644 (file)
@@ -592,7 +592,7 @@ void VirtualStudio::setWindowState(QString state)
     m_windowState = state;
     // refresh studio list if navigating to browse window
     // only if user id is empty (edge case for when logging in)
-    if (m_windowState == "browse" && !m_userId.isEmpty()) {
+    if (m_windowState == "browse" && m_auth->isAuthenticated()) {
         // schedule studio refresh instead of doing it now
         // just to reduce risk of running into a deadlock
         emit scheduleStudioRefresh(-1, false);
@@ -655,26 +655,22 @@ void VirtualStudio::collectFeedbackSurvey(QString serverId, int rating, QString
 #endif
 
     feedback.insert(QStringLiteral("osVersion"), QSysInfo::prettyProductName());
-    QString sysInfo = QString("[platform=%1").arg(QSysInfo::prettyProductName());
 #ifdef RT_AUDIO
     QString inputDevice =
         QString::fromStdString(m_audioConfigPtr->getInputDevice().toStdString());
     if (!inputDevice.isEmpty()) {
-        sysInfo.append(QString(",input=%1").arg(inputDevice));
+        feedback.insert(QStringLiteral("inputDevice"), inputDevice);
     }
     QString outputDevice =
         QString::fromStdString(m_audioConfigPtr->getOutputDevice().toStdString());
     if (!outputDevice.isEmpty()) {
-        sysInfo.append(QString(",output=%1").arg(outputDevice));
+        feedback.insert(QStringLiteral("outputDevice"), outputDevice);
     }
 #endif
-    sysInfo.append("]");
 
     feedback.insert(QStringLiteral("rating"), rating);
-    if (message.isEmpty()) {
-        feedback.insert(QStringLiteral("message"), sysInfo);
-    } else {
-        feedback.insert(QStringLiteral("message"), message + " " + sysInfo);
+    if (!message.isEmpty()) {
+        feedback.insert(QStringLiteral("message"), message);
     }
 
     QString deviceIssues  = "";
@@ -687,11 +683,11 @@ void VirtualStudio::collectFeedbackSurvey(QString serverId, int rating, QString
     }
     if (!deviceIssues.isEmpty()) {
         feedback.insert(QStringLiteral("deviceIssues"), deviceIssues);
-        message.append(" (deviceIssues=" + deviceIssues + ")");
     }
 
     QJsonDocument data = QJsonDocument(feedback);
     m_api->submitServerFeedback(serverId, data.toJson());
+    std::cout << "Sent feedback: " << data.toJson().toStdString() << std::endl;
     return;
 }
 
@@ -857,17 +853,13 @@ void VirtualStudio::joinStudio()
         return;
     }
 
-    // pop studioToJoin
-    const QString targetId = m_studioToJoin;
-    setStudioToJoin("");
-
     // stop audio if already running (settings or setup windows)
     m_audioConfigPtr->stopAudio(true);
 
     // find and populate data for current studio
     VsServerInfoPointer sPtr;
     for (const VsServerInfoPointer& s : m_servers) {
-        if (s->id() == targetId) {
+        if (s->id() == m_studioToJoin) {
             sPtr = s;
             break;
         }
@@ -875,21 +867,23 @@ void VirtualStudio::joinStudio()
     locker.unlock();
 
     if (sPtr.isNull()) {
-        m_failedMessage = "Unable to find studio " + targetId;
+        m_failedMessage = "Unable to find studio " + m_studioToJoin;
+        setStudioToJoin("");
         emit failedMessageChanged();
         emit failed();
         return;
     }
 
-    m_currentStudio = *sPtr;
-    emit currentStudioChanged();
-
     if (m_windowState == "setup") {
-        m_audioConfigPtr->setSampleRate(m_currentStudio.sampleRate());
+        m_audioConfigPtr->setSampleRate(sPtr->sampleRate());
         m_audioConfigPtr->startAudio();
         return;
     }
 
+    setStudioToJoin("");
+    m_currentStudio = *sPtr;
+    emit currentStudioChanged();
+
     // m_windowState == "connected"
     connectToStudio();
 }
@@ -1168,6 +1162,9 @@ void VirtualStudio::triggerReconnect(bool refresh)
         return;
     }
 
+    std::cout << "Reconnecting audio to " << m_currentStudio.host().toStdString() << ":"
+              << m_currentStudio.port() << std::endl;
+
     // this needs to be synchronous to avoid both trying
     // to use the audio interfaces at the same time
     // note that connectionFinished() checks m_reconnectState
@@ -1501,11 +1498,18 @@ void VirtualStudio::receivedConnectionFromPeer()
 
 void VirtualStudio::handleWebsocketMessage(const QString& msg)
 {
-    QJsonObject serverState = QJsonDocument::fromJson(msg.toUtf8()).object();
-    QString serverStatus    = serverState[QStringLiteral("status")].toString();
-    bool serverEnabled      = serverState[QStringLiteral("enabled")].toBool();
-    QString serverCloudId   = serverState[QStringLiteral("cloudId")].toString();
-    int queueBuffer         = serverState[QStringLiteral("queueBuffer")].toInt();
+    if (m_currentStudio.id() == "") {
+        return;
+    }
+
+    QJsonObject serverState      = QJsonDocument::fromJson(msg.toUtf8()).object();
+    const QString& serverHost    = serverState[QStringLiteral("serverHost")].toString();
+    const QString& serverStatus  = serverState[QStringLiteral("status")].toString();
+    const QString& serverCloudId = serverState[QStringLiteral("cloudId")].toString();
+    const QString& sessionId     = serverState[QStringLiteral("sessionId")].toString();
+    const bool serverEnabled     = serverState[QStringLiteral("enabled")].toBool();
+    const int serverPort         = serverState[QStringLiteral("serverPort")].toInt();
+    const int queueBuffer        = serverState[QStringLiteral("queueBuffer")].toInt();
 
     // server notifications are also transmitted along this websocket, so ignore data if
     // it contains "message"
@@ -1513,26 +1517,55 @@ void VirtualStudio::handleWebsocketMessage(const QString& msg)
     if (!message.isEmpty()) {
         return;
     }
-    if (m_currentStudio.id() == "") {
-        return;
+
+    bool currentStudioUpdated    = false;
+    bool serverHostOrPortUpdated = false;
+    if (serverHost != m_currentStudio.host()) {
+        m_currentStudio.setHost(serverHost);
+        currentStudioUpdated    = true;
+        serverHostOrPortUpdated = true;
     }
-    m_currentStudio.setStatus(serverStatus);
-    m_currentStudio.setEnabled(serverEnabled);
-    m_currentStudio.setCloudId(serverCloudId);
-    m_currentStudio.setQueueBuffer(queueBuffer);
-    if (!m_jackTripRunning) {
-        if (serverStatus == QLatin1String("Ready") && m_onConnectedScreen) {
-            m_currentStudio.setHost(serverState[QStringLiteral("serverHost")].toString());
-            m_currentStudio.setPort(serverState[QStringLiteral("serverPort")].toInt());
-            m_currentStudio.setSessionId(
-                serverState[QStringLiteral("sessionId")].toString());
-            completeConnection();
+    if (serverStatus != m_currentStudio.status()) {
+        m_currentStudio.setStatus(serverStatus);
+        currentStudioUpdated = true;
+    }
+    if (serverCloudId != m_currentStudio.cloudId()) {
+        m_currentStudio.setCloudId(serverCloudId);
+        currentStudioUpdated = true;
+    }
+    if (sessionId != m_currentStudio.sessionId()) {
+        m_currentStudio.setSessionId(sessionId);
+        currentStudioUpdated = true;
+    }
+    if (serverEnabled != m_currentStudio.enabled()) {
+        m_currentStudio.setEnabled(serverEnabled);
+        currentStudioUpdated = true;
+    }
+    if (serverPort != m_currentStudio.port()) {
+        m_currentStudio.setPort(serverPort);
+        currentStudioUpdated    = true;
+        serverHostOrPortUpdated = true;
+    }
+    if (queueBuffer != m_currentStudio.queueBuffer()) {
+        m_currentStudio.setQueueBuffer(queueBuffer);
+        currentStudioUpdated = true;
+        if (m_useStudioQueueBuffer && !m_devicePtr.isNull()) {
+            m_devicePtr->setQueueBuffer(m_currentStudio.queueBuffer());
         }
-    } else if (m_useStudioQueueBuffer && !m_devicePtr.isNull()) {
-        m_devicePtr->setQueueBuffer(m_currentStudio.queueBuffer());
     }
 
-    emit currentStudioChanged();
+    if (currentStudioUpdated) {
+        emit currentStudioChanged();
+    }
+
+    if (m_onConnectedScreen) {
+        if (!m_jackTripRunning && serverEnabled && serverStatus == QLatin1String("Ready")
+            && serverHost != "" && serverPort != 0) {
+            std::cout << "Connecting audio to " << serverHost.toStdString() << ":"
+                      << serverPort << std::endl;
+            completeConnection();
+        }
+    }
 }
 
 void VirtualStudio::restartStudioSocket()
@@ -1586,7 +1619,7 @@ void VirtualStudio::resetState()
 void VirtualStudio::refreshStudios(int index, bool signalRefresh)
 {
     // user id is required for retrieval of subscriptions
-    if (m_userId.isEmpty()) {
+    if (!m_auth->isAuthenticated()) {
         std::cerr << "Studio refresh cancelled due to empty user id" << std::endl;
         return;
     }
@@ -1873,6 +1906,7 @@ VirtualStudio::~VirtualStudio()
 QApplication* VirtualStudio::createApplication(int& argc, char* argv[])
 {
 #if defined(Q_OS_WIN)
+#if QT_VERSION < QT_VERSION_CHECK(6, 6, 0)
     // Fix for display scaling like 125% or 150% on Windows
     QGuiApplication::setHighDpiScaleFactorRoundingPolicy(
         Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
@@ -1882,8 +1916,12 @@ QApplication* VirtualStudio::createApplication(int& argc, char* argv[])
     // QCoreApplication::setAttribute(Qt::AA_UseDesktopOpenGL);
     // QCoreApplication::setAttribute(Qt::AA_UseOpenGLES);
 
+    // Direct3D11 is still broken as of Qt 6.8.1
     // QQuickWindow::setGraphicsApi(QSGRendererInterface::Direct3D11);
     QQuickWindow::setGraphicsApi(QSGRendererInterface::OpenGL);
+#else  // Qt 6.6.0 or later supports Direct3D 12, which works well
+    QQuickWindow::setGraphicsApi(QSGRendererInterface::Direct3D12);
+#endif
 #endif
 
     QQuickStyle::setStyle("Basic");
@@ -1891,7 +1929,13 @@ QApplication* VirtualStudio::createApplication(int& argc, char* argv[])
 #if defined(Q_OS_MACOS) && (QT_VERSION > QT_VERSION_CHECK(6, 2, 6)) \
     && (QT_VERSION < QT_VERSION_CHECK(6, 8, 0))
     // work-around for screen sharing bugs in qtwebengine 6.2.7-6.7.x
-    qputenv("QTWEBENGINE_CHROMIUM_FLAGS", "--disable-features=DesktopCaptureMacV2");
+    QString chromiumFlags("--disable-features=DesktopCaptureMacV2");
+    char* existingFlags = getenv("QTWEBENGINE_CHROMIUM_FLAGS");
+    if (existingFlags != nullptr) {
+        chromiumFlags.append(" ");
+        chromiumFlags.append(existingFlags);
+    }
+    qputenv("QTWEBENGINE_CHROMIUM_FLAGS", chromiumFlags.toUtf8());
 #endif
 
     // Initialize webengine
index d244238447bead17e2e5548dccef9284d3f33908..09e34e945df02b25ced23ebd565e9ed7e3afe62f 100644 (file)
@@ -315,6 +315,16 @@ void VsAudio::setInputMixMode(const int mode)
     if (mode == m_inputMixMode)
         return;
     m_inputMixMode = mode;
+    if (m_inputMixMode == static_cast<int>(AudioInterface::MONO)) {
+        if (m_numInputChannels > 1) {
+            setNumInputChannels(1);
+        }
+    } else if (m_inputMixMode == static_cast<int>(AudioInterface::STEREO)
+               || m_inputMixMode == static_cast<int>(AudioInterface::MIXTOMONO)) {
+        if (m_numInputChannels == 1) {
+            setNumInputChannels(2);
+        }
+    }
     emit inputMixModeChanged(mode);
     return;
 }
@@ -863,6 +873,11 @@ AudioInterface* VsAudio::newAudioInterface(JackTrip* jackTripPtr)
     std::cout << "                      or: " << AudioBufferSizeInBytes << " bytes"
               << std::endl;
     std::cout << gPrintSeparator << std::endl;
+    std::cout << "The Audio Input Latency is : " << ifPtr->getAudioInputLatency()
+              << std::endl;
+    std::cout << "The Audio Output Latency is: " << ifPtr->getAudioOutputLatency()
+              << std::endl;
+    std::cout << gPrintSeparator << std::endl;
     std::cout << "The Number of Channels is: " << ifPtr->getNumInputChannels()
               << std::endl;
     std::cout << gPrintSeparator << std::endl;
@@ -1302,17 +1317,13 @@ void VsAudioWorker::validateInputDevicesState()
             inputChannelsComboModel.push_back(element);
         }
         for (int i = 0; i < numDevicesChannelsAvailable; i++) {
-            if (i % 2 == 0) {
-                QJsonObject element = QJsonObject();
-                element.insert(
-                    QString::fromStdString("label"),
-                    QVariant(i + 1).toString() + " & " + QVariant(i + 2).toString());
-                element.insert(QString::fromStdString("baseChannel"),
-                               QVariant(i).toInt());
-                element.insert(QString::fromStdString("numChannels"),
-                               QVariant(2).toInt());
-                inputChannelsComboModel.push_back(element);
-            }
+            QJsonObject element = QJsonObject();
+            element.insert(
+                QString::fromStdString("label"),
+                QVariant(i + 1).toString() + " & " + QVariant(i + 2).toString());
+            element.insert(QString::fromStdString("baseChannel"), QVariant(i).toInt());
+            element.insert(QString::fromStdString("numChannels"), QVariant(2).toInt());
+            inputChannelsComboModel.push_back(element);
         }
         m_parentPtr->setInputChannelsComboModel(inputChannelsComboModel);
 
@@ -1324,26 +1335,31 @@ void VsAudioWorker::validateInputDevicesState()
             m_parentPtr->setBaseInputChannel(0);
             m_parentPtr->setNumInputChannels(2);
         }
-        if (getNumInputChannels() != 1) {
-            // Set the input mix mode to have two options: "Stereo" and "Mix to Mono" if
-            // we're using 2 channels
-            QJsonObject inputMixModeComboElement1 = QJsonObject();
-            inputMixModeComboElement1.insert(QString::fromStdString("label"),
-                                             QString::fromStdString("Stereo"));
-            inputMixModeComboElement1.insert(QString::fromStdString("value"),
-                                             static_cast<int>(AudioInterface::STEREO));
-            QJsonObject inputMixModeComboElement2 = QJsonObject();
-            inputMixModeComboElement2.insert(QString::fromStdString("label"),
-                                             QString::fromStdString("Mix to Mono"));
-            inputMixModeComboElement2.insert(QString::fromStdString("value"),
-                                             static_cast<int>(AudioInterface::MIXTOMONO));
-            QJsonArray inputMixModeComboModel;
-            inputMixModeComboModel.push_back(inputMixModeComboElement1);
-            inputMixModeComboModel.push_back(inputMixModeComboElement2);
-            m_parentPtr->setInputMixModeComboModel(inputMixModeComboModel);
-
-            // if m_inputMixMode is an invalid value, set it to "stereo" by default
-            // given that we are using 2 channels
+
+        // include all options in the mix mode combo
+        QJsonObject inputMixModeComboElement0 = QJsonObject();
+        inputMixModeComboElement0.insert(QString::fromStdString("label"),
+                                         QString::fromStdString("Mono"));
+        inputMixModeComboElement0.insert(QString::fromStdString("value"),
+                                         static_cast<int>(AudioInterface::MONO));
+        QJsonObject inputMixModeComboElement1 = QJsonObject();
+        inputMixModeComboElement1.insert(QString::fromStdString("label"),
+                                         QString::fromStdString("Stereo"));
+        inputMixModeComboElement1.insert(QString::fromStdString("value"),
+                                         static_cast<int>(AudioInterface::STEREO));
+        QJsonObject inputMixModeComboElement2 = QJsonObject();
+        inputMixModeComboElement2.insert(QString::fromStdString("label"),
+                                         QString::fromStdString("Mix to Mono"));
+        inputMixModeComboElement2.insert(QString::fromStdString("value"),
+                                         static_cast<int>(AudioInterface::MIXTOMONO));
+        QJsonArray inputMixModeComboModel;
+        inputMixModeComboModel.push_back(inputMixModeComboElement0);
+        inputMixModeComboModel.push_back(inputMixModeComboElement1);
+        inputMixModeComboModel.push_back(inputMixModeComboElement2);
+        m_parentPtr->setInputMixModeComboModel(inputMixModeComboModel);
+
+        if (m_parentPtr->getNumInputChannels() == 2) {
+            // Set the input mix mode to "Stereo" if we're using 2 channels
             if (getInputMixMode() != static_cast<int>(AudioInterface::STEREO)
                 && getInputMixMode() != static_cast<int>(AudioInterface::MIXTOMONO)) {
                 m_parentPtr->setInputMixMode(static_cast<int>(AudioInterface::STEREO));
@@ -1351,16 +1367,6 @@ void VsAudioWorker::validateInputDevicesState()
         } else {
             // Set the input mix mode to just have "Mono" as the option if we're using 1
             // channel
-            QJsonObject inputMixModeComboElement = QJsonObject();
-            inputMixModeComboElement.insert(QString::fromStdString("label"),
-                                            QString::fromStdString("Mono"));
-            inputMixModeComboElement.insert(QString::fromStdString("value"),
-                                            static_cast<int>(AudioInterface::MONO));
-            QJsonArray inputMixModeComboModel;
-            inputMixModeComboModel.push_back(inputMixModeComboElement);
-            m_parentPtr->setInputMixModeComboModel(inputMixModeComboModel);
-
-            // if m_inputMixMode is an invalid value, set it to AudioInterface::MONO
             if (getInputMixMode() != static_cast<int>(AudioInterface::MONO)) {
                 m_parentPtr->setInputMixMode(static_cast<int>(AudioInterface::MONO));
             }
@@ -1418,17 +1424,13 @@ void VsAudioWorker::validateOutputDevicesState()
         // selected device
         QJsonArray outputChannelsComboModel;
         for (int i = 0; i < numDevicesChannelsAvailable; i++) {
-            if (i % 2 == 0) {
-                QJsonObject element = QJsonObject();
-                element.insert(
-                    QString::fromStdString("label"),
-                    QVariant(i + 1).toString() + " & " + QVariant(i + 2).toString());
-                element.insert(QString::fromStdString("baseChannel"),
-                               QVariant(i).toInt());
-                element.insert(QString::fromStdString("numChannels"),
-                               QVariant(2).toInt());
-                outputChannelsComboModel.push_back(element);
-            }
+            QJsonObject element = QJsonObject();
+            element.insert(
+                QString::fromStdString("label"),
+                QVariant(i + 1).toString() + " & " + QVariant(i + 2).toString());
+            element.insert(QString::fromStdString("baseChannel"), QVariant(i).toInt());
+            element.insert(QString::fromStdString("numChannels"), QVariant(2).toInt());
+            outputChannelsComboModel.push_back(element);
         }
         m_parentPtr->setOutputChannelsComboModel(outputChannelsComboModel);
 
index 8e49fd6179844b8de1e73cb9d9e86662b4b5c658..39d7cc9c9f11f1e20594760f9922ac6d5bb9074b 100644 (file)
@@ -189,8 +189,17 @@ void VsDevice::sendHeartbeat()
         json.insert(QLatin1String("high_latency"),
                     m_audioConfigPtr->getHighLatencyFlag());
         json.insert(QLatin1String("network_outage"), m_networkOutage);
-        json.insert(QLatin1String("recv_latency"),
-                    m_jackTrip.isNull() ? -1 : m_jackTrip->getLatency());
+        json.insert(QLatin1String("audio_input_latency"),
+                    m_jackTrip.isNull()
+                        ? 0
+                        : (qint64)(m_jackTrip->getAudioInputLatency() * 10000));
+        json.insert(QLatin1String("audio_output_latency"),
+                    m_jackTrip.isNull()
+                        ? 0
+                        : (qint64)(m_jackTrip->getAudioOutputLatency() * 10000));
+        json.insert(
+            QLatin1String("client_buffer_latency"),
+            m_jackTrip.isNull() ? 0 : (qint64)(m_jackTrip->getLatency() * ns_per_ms));
 
         // For the internal application UI, ms will suffice. No conversion needed
         QJsonObject pingStats = {};
@@ -203,8 +212,13 @@ void VsDevice::sendHeartbeat()
                          ((int)(10 * stats.stdDevRtt)) / 10.0);
         pingStats.insert(QLatin1String("highLatency"),
                          m_audioConfigPtr->getHighLatencyFlag());
-        pingStats.insert(QLatin1String("recvLatency"),
-                         m_jackTrip.isNull() ? -1 : m_jackTrip->getLatency());
+        pingStats.insert(QLatin1String("audioInputLatency"),
+                         m_jackTrip.isNull() ? 0 : m_jackTrip->getAudioInputLatency());
+        pingStats.insert(QLatin1String("audioOutputLatency"),
+                         m_jackTrip.isNull() ? 0 : m_jackTrip->getAudioOutputLatency());
+        pingStats.insert(
+            QLatin1String("clientBufferLatency"),
+            m_jackTrip.isNull() ? 0 : ((int)(10 * m_jackTrip->getLatency())) / 10.0);
         emit updateNetworkStats(pingStats);
     }